package gamer2;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLException;

import jogamp.opengl.SharedResourceRunner.Resource;

import utils.Vec;

import com.jogamp.common.nio.Buffers;


public class Shader {

	private GL2 m_gl = null;
	private int m_shaderprogram, m_vert, m_frag, m_geom;

	public final int GetShaderProgram() {
		return m_shaderprogram;
	}

	public Shader(final GL2 gl, final String vert, final String frag,
			final String includeFile) {
		m_gl = gl;
		InitializeFromFile(vert, frag, includeFile);

	}

	public Shader(final GL2 gl, final String vert, final String frag,
			final String geom, final String include) {
		m_gl = gl;
		InitializeFromFileGeom(vert, frag, geom, include);

	}

	
	public void BindAttrib(int i, String name) {
//		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glBindAttribLocation(m_shaderprogram, i, name);
		
	}
	
	public void DisableMultitextures() {
		m_gl.glActiveTexture(GL2.GL_TEXTURE0);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE1);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE2);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE3);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE4);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE5);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE6);
		m_gl.glDisable(GL2.GL_TEXTURE_2D);
		m_gl.glActiveTexture(GL2.GL_TEXTURE0);
	}


	public void SetupMultitexture(CTexture texture, String name, int number, boolean wrap) {


		
		if (number == 0)
			m_gl.glActiveTexture(GL2.GL_TEXTURE0);
		if (number == 1)
			m_gl.glActiveTexture(GL2.GL_TEXTURE1);
		if (number == 2)
			m_gl.glActiveTexture(GL2.GL_TEXTURE2);
		if (number == 3)
			m_gl.glActiveTexture(GL2.GL_TEXTURE3);
		if (number == 4)
			m_gl.glActiveTexture(GL2.GL_TEXTURE4);
		if (number == 5)
			m_gl.glActiveTexture(GL2.GL_TEXTURE5);
		if (number == 6)
			m_gl.glActiveTexture(GL2.GL_TEXTURE6);
		// m_gl.glEnable(GL2.GL_TEXTURE_2D);

		if (texture.texture!=null) {
			texture.texture.enable(m_gl);
			texture.texture.bind(m_gl);
//			texture.texture.setTexParameteri(m_gl, GL2.GL_TEXTURE_MIN_FILTER,GL2.GL_LINEAR);
/*			texture.texture.setTexParameteri(m_gl,GL2.GL_TEXTURE_BASE_LEVEL, 5);
			texture.texture.setTexParameteri(m_gl,GL2.GL_TEXTURE_MAX_LEVEL, 5);*/
		}
		else {
			texture.Enable(m_gl);
		}
//		texture.Enable(m_gl);
		
		
		if (wrap) {
			m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T,
				GL2.GL_REPEAT);
			m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S,
				GL2.GL_REPEAT);
		}
		else
		{
			m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T,
					GL2.GL_CLAMP_TO_EDGE);
			m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S,
					GL2.GL_CLAMP_TO_EDGE);
		}
		// m_gl.glTexParameterf(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER,
		// GL2.GL_LINEAR);
		m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER,
				GL2.GL_LINEAR_MIPMAP_LINEAR);
		// m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER,
		// GL2.GL_LINEAR);
		m_gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER,
				GL2.GL_LINEAR_MIPMAP_LINEAR);

		
		
		
		
		

		SetUniformParameter1i(name, number);

	}

	public void Start() {
		m_gl.glUseProgram(m_shaderprogram);
	}

	public void Stop() {
		m_gl.glUseProgram(0);
	}

	public void SetUniformParameter3f(final String s, final float x,
			final float y, final float z) {
		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glUniform3f(loc, x, y, z);
	}

	public void SetUniformParameter3f(final String s, final Vec v) {
		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glUniform3f(loc, (float) v.x(), (float) v.y(), (float) v.z());
	}

	public void SetUniformParameter1f(final String s, final float x) {
		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glUniform1f(loc, x);
	}

	public void SetUniformParameter1i(final String s, final int x) {
		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glUniform1i(loc, x);
	}

	public void SetUniformParameterMatrix4fv(final String s, FloatBuffer f) {
		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glUniformMatrix4fv(loc, 1, false, f);
	}

	public void SetUniformParameterMatrix3fv(final String s, FloatBuffer f) {
		int loc = m_gl.glGetUniformLocation(m_shaderprogram, s);
		m_gl.glUniformMatrix3fv(loc, 1, false, f);
	}

	
	public void setupShaderTexture(int texture_number, String variable_name) {
		int txt = GL2.GL_TEXTURE0;
		switch (texture_number) {
		case 0:
			txt = GL2.GL_TEXTURE0;
			break;
		case 1:
			txt = GL2.GL_TEXTURE1;
			break;
		case 2:
			txt = GL2.GL_TEXTURE2;
			break;
		case 3:
			txt = GL2.GL_TEXTURE3;
			break;
		case 4:
			txt = GL2.GL_TEXTURE4;
			break;
		case 5:
			txt = GL2.GL_TEXTURE5;
			break;
		case 6:
			txt = GL2.GL_TEXTURE6;
			break;
		case 7:
			txt = GL2.GL_TEXTURE7;
			break;
		}

		int ref = m_gl.glGetUniformLocation(m_shaderprogram, variable_name);
		m_gl.glUniform1i(ref, texture_number);
		m_gl.glActiveTexture(txt);
		m_gl.glEnable(GL2.GL_TEXTURE_2D);
		// glBindTexture(GL_TEXTURE_2D, texture);
	}

	public void InitializeFromFile(final String vert, final String frag) {
		InitializeFromFile(vert, frag, "");
	}

	private String compileString(String file) {
		String src = "";
		String line;
		try {
			file = "shaders/" + file +".sh";
			
			InputStream in = this.getClass().getClassLoader().getResourceAsStream(file);
			InputStreamReader ivr = new InputStreamReader(in);
			BufferedReader brv = new BufferedReader(ivr);
			while ((line = brv.readLine()) != null) {
				src += line + "\n";
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println(e.getMessage());
			System.exit(1);
		}
		return src;
	}

	public void InitializeFromFile(final String vert, final String frag,
			final String includeFile) {

		m_vert = m_gl.glCreateShader(GL2.GL_VERTEX_SHADER);
		m_frag = m_gl.glCreateShader(GL2.GL_FRAGMENT_SHADER);

		String header = "#version 120\n";
		header = "";
		String include = "";
		if (!includeFile.equals(""))
			include = compileString(includeFile) + "\n";

		String vsrc = header + include + compileString(vert);
		try {
			m_gl.glShaderSource(m_vert, 1, new String[] { vsrc }, (int[]) null,
					0);
			m_gl.glCompileShader(m_vert);
			checkShaderLogInfo(m_vert);
		} catch (GLException e) {
			System.out.println("Exception compiling: " + vert);
			System.exit(1);
		}

		String fsrc = header + include + compileString(frag);
		try {
			m_gl.glShaderSource(m_frag, 1, new String[] { fsrc }, (int[]) null,
					0);
			m_gl.glCompileShader(m_frag);
			checkShaderLogInfo(m_frag);
		} catch (GLException e) {
			System.out.println("Exception compiling: " + frag);
			System.exit(1);
		}

		m_shaderprogram = m_gl.glCreateProgram();

		m_gl.glAttachShader(m_shaderprogram, m_vert);
		m_gl.glAttachShader(m_shaderprogram, m_frag);
		m_gl.glLinkProgram(m_shaderprogram);
		m_gl.glValidateProgram(m_shaderprogram);
		// m_gl.glUseProgram(m_shaderprogram);

	}

	public void InitializeFromFileGeom(final String vert, final String frag,
			final String geom, final String includeFile) {

		m_vert = m_gl.glCreateShader(GL2.GL_VERTEX_SHADER);
		m_frag = m_gl.glCreateShader(GL2.GL_FRAGMENT_SHADER);
		m_geom = m_gl.glCreateShader(GL2.GL_GEOMETRY_SHADER_ARB);

		try {

		//	String header = "#version 150\n";
			String header = "#version 330 \n #extension GL_ARB_explicit_attrib_location : enable";
			String include = "";
//			String header ="";
			
			
			if (!includeFile.equals(""))
				include = compileString(includeFile) + "\n";

			String vsrc = header + include + compileString(vert);

			m_gl.glShaderSource(m_vert, 1, new String[] { vsrc }, (int[]) null,
					0);

			try {
			
			m_gl.glCompileShader(m_vert);
			checkShaderLogInfo(m_vert);
			} catch (GLException e) {
				System.out.println("Exception compiling: " + vert);
				System.out.println(e.getMessage());
				System.exit(1);
			}

			String fsrc = header + include + compileString(frag);

			m_gl.glShaderSource(m_frag, 1, new String[] { fsrc }, (int[]) null,
					0);

			try {
				m_gl.glCompileShader(m_frag);
				checkShaderLogInfo(m_frag);
			} catch (GLException e) {
				System.out.println("Exception compiling: " +frag);
				System.out.println(e.getMessage());
				System.exit(1);
			}

			
			String gsrc = header + include + compileString(geom);

			m_gl.glShaderSource(m_geom, 1, new String[] { gsrc }, (int[]) null,
					0);

			try {
			m_gl.glCompileShader(m_geom);
			checkShaderLogInfo(m_geom);
			} catch (GLException e) {
				System.out.println("Exception compiling: " + geom);
				System.out.println(e.getMessage());
				System.exit(1);
			}

		
		} catch (GLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println(e.getMessage());
			System.exit(1);
		}


/*		m_gl.glProgramParameteri(m_shaderprogram,
				GL2.GL_GEOMETRY_INPUT_TYPE_EXT, GL.GL_TRIANGLES);

		m_gl.glProgramParameteri(m_shaderprogram,
				GL2.GL_GEOMETRY_OUTPUT_TYPE_EXT, GL.GL_TRIANGLES);
		
		m_gl.glProgramParameteri(m_shaderprogram,
				GL2.GL_GEOMETRY_OUTPUT_TYPE_EXT, GL.GL_LINE_STRIP);
*/
		

		System.out.println("DONE!");

		

		
		
		int[] temp = new int[2];
		int[] temp2 = new int[2];
		m_gl.glGetIntegerv(GL2.GL_MAX_GEOMETRY_OUTPUT_VERTICES_ARB, temp, 0);
		m_gl.glGetIntegerv(GL2.GL_MAX_GEOMETRY_VARYING_COMPONENTS_ARB, temp2, 0);

		System.out.println("MAX: " + temp[0]);
		System.out.println("MAXC: " + temp2[0]);
		int s = 3;
		temp[0] = s * s * 2 * 3;
		// temp[0] = 80;
		// temp[0] = 3;
		m_gl.glProgramParameteri(m_shaderprogram,
				GL2.GL_GEOMETRY_VERTICES_OUT_ARB, temp[0]);
		System.out.println("NO VERTICES: " + temp[0]);


		try {

			System.out.println("1.");
			m_shaderprogram = m_gl.glCreateProgram();

			System.out.println("2.");
			m_gl.glAttachShader(m_shaderprogram, m_vert);
			m_gl.glAttachShader(m_shaderprogram, m_geom);
			m_gl.glAttachShader(m_shaderprogram, m_frag);

			
			System.out.println("3.");
			m_gl.glProgramParameteri(m_shaderprogram,
					GL2.GL_GEOMETRY_INPUT_TYPE_EXT, GL.GL_POINTS);

			m_gl.glProgramParameteri(m_shaderprogram,
					GL2.GL_GEOMETRY_OUTPUT_TYPE_EXT, GL.GL_TRIANGLES);

			System.out.println("4.");
			m_gl.glLinkProgram(m_shaderprogram);

		
			System.out.println("5.");
			//checkProgramLogInfo(m_shaderprogram);
			System.out.println("6.");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println("ERROR DURING GEOM SHADER:");
			e.printStackTrace();
			System.out.println(e.getMessage());
			System.exit(1);
	}


		// m_gl.glUseProgram(m_shaderprogram);

	}

	private void checkShaderLogInfo(int program) {
		IntBuffer tReturnValue = Buffers.newDirectIntBuffer(1);
		m_gl.glGetShaderiv(program, GL2.GL_COMPILE_STATUS, tReturnValue);
		if (tReturnValue.get(0) == GL2.GL_FALSE) {
			m_gl.glGetShaderiv(program, GL2.GL_INFO_LOG_LENGTH, tReturnValue);
			int length = tReturnValue.get(0);
			String out = null;
			if (length > 0) {
				final ByteBuffer infoLog = Buffers.newDirectByteBuffer(length);
				m_gl.glGetShaderInfoLog(program, infoLog.limit(), tReturnValue,
						infoLog);
				final byte[] infoBytes = new byte[length];
				infoLog.get(infoBytes);
				out = new String(infoBytes);
				System.out.print(out);
			}
			throw new GLException("Error during shader compilation: " + length
					+ "   - " + out);
		}

	}

	private void checkProgramLogInfo(int program) {
		IntBuffer tReturnValue = Buffers.newDirectIntBuffer(1);
		m_gl.glGetProgramiv(program, GL2.GL_COMPILE_STATUS, tReturnValue);
		if (tReturnValue.get(0) == GL2.GL_FALSE) {
			m_gl.glGetProgramiv(program, GL2.GL_INFO_LOG_LENGTH, tReturnValue);
			final int length = tReturnValue.get(0);
			String out = null;
			if (length > 0) {
				final ByteBuffer infoLog = Buffers.newDirectByteBuffer(length);
				m_gl.glGetProgramInfoLog(program, infoLog.limit(),
						tReturnValue, infoLog);
				final byte[] infoBytes = new byte[length];
				infoLog.get(infoBytes);
				out = new String(infoBytes);
				System.out.println("ERROR : " + out);
			}
			if (length != 0) {
			
//				System.out.println(length + ": " + out);
//				System.exit(1);
				
				throw new GLException("Error during program linking: " + length
						+ "   - " + out);
			}
		}

	}

}
